//----------------------------------------------------------------------------
// main.cpp
//
// nclip clipboard munging utility
//
// Copyright (C) 20011 Neil Butterworth
//----------------------------------------------------------------------------

#include <exception>
#include <cstdio>
#include <iostream>
#include <iomanip>
#include <string>
#include <sstream>
#include <vector>
#include <windows.h>

#include "cmdline.h"
#include "error.h"
#include "mon.h"
#include "util.h"
#include "bmcmd.h"

using namespace std;

//----------------------------------------------------------------------------
// Version string
//----------------------------------------------------------------------------

const char * const VERSION = "0.4";

//----------------------------------------------------------------------------
// Are we in verbose mode?
//----------------------------------------------------------------------------

static bool Verbose = false;

//----------------------------------------------------------------------------
// Are we in append mode for commands that set clipboard text?
//----------------------------------------------------------------------------

static bool AppendMode = false;

//----------------------------------------------------------------------------
// Strings used for reporting status
//----------------------------------------------------------------------------

const char * const YES_STR 	= "yes";
const char * const NO_STR 	= "no";

//----------------------------------------------------------------------------
// Helper to output the correct string depending if we are in verbose mode
// and on what the return value is. Remember these are shell exit status
// values, not ints acting as booleans!
//----------------------------------------------------------------------------

int YesNo( int rv ) {
	if ( Verbose ) {
		if ( rv == 0 ) {
			cout << YES_STR << endl;
		}
		else {
			cout << NO_STR << endl;
		}
	}
	return rv;
}

//----------------------------------------------------------------------------
// Helper to display a Windows message box
//----------------------------------------------------------------------------

void MsgBox( const string & text, UINT opt ) {
	MessageBox( 0, text.c_str(), "nclip",  opt );
}

//----------------------------------------------------------------------------
// Commands. These must each create the clipboard managing object as
// appropriate. Some commands can hang, and we do not want to hold the
// clipboard open, except when specifically required.
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
// Get clipboard contents as text and display it
//----------------------------------------------------------------------------

int GetDataCmd( CmdLine & ) {
	Clipboard clip;
	cout << GetTextData();
	return 0;
}

//----------------------------------------------------------------------------
// Get clipboard contents as text and display it in message box
//----------------------------------------------------------------------------

int MsgBoxCmd( CmdLine & ) {
	Clipboard clip;
	if ( ! HasFormat( CF_TEXT ) ) {
		MsgBox( "No text in clipboard", MB_OK | MB_ICONSTOP );
	}
	else {
		MsgBox( GetTextData(), MB_OK | MB_ICONINFORMATION );
	}
	return 0;
}


//----------------------------------------------------------------------------
// Copy current directory path in Windows format to clipboard as text.
//----------------------------------------------------------------------------

int CurrentDirCmd( CmdLine & ) {
	Clipboard clip;
	char path[ MAX_PATH + 1 ];
	DWORD rv = GetCurrentDirectory( MAX_PATH, path );
	if ( rv == 0 ) {
		throw ClipError( LastWinError() );
	}
	SetTextData( path, AppendMode );
	return 0;
}

//----------------------------------------------------------------------------
// Clear any clipboard contents
//----------------------------------------------------------------------------

int ClearCmd( CmdLine & ) {
	Clipboard clip;
	::EmptyClipboard();
	return 0;
}

//----------------------------------------------------------------------------
// Test if clipboard has any content
//----------------------------------------------------------------------------

int EmptyCmd( CmdLine & ) {
	Clipboard clip;
	if ( ::EnumClipboardFormats( 0 ) ) {
		return YesNo( 1 );
	}
	else {
		return YesNo( 0 );
	}
}

//----------------------------------------------------------------------------
// Set clipboard to contain text specifed as command line parameters
//----------------------------------------------------------------------------

int SetCmd( CmdLine & cl ) {
	Clipboard clip;
	if ( cl.Argc() < 3 ) {
		throw ClipError( "No value specified" );
	}

	string text;
	for ( int i = 2; i < cl.Argc(); i++ ) {
		if ( text != "" ) {
			text += " ";
		}
		text += cl.Argv(i);
	}

	SetTextData( text, AppendMode );
	return 0;
}

//----------------------------------------------------------------------------
// See if clipboard contains any of the supplied list of formats
//----------------------------------------------------------------------------

int  HasFormatsCmd( CmdLine & cl ) {

	Clipboard clip;
	if ( cl.Argc() <= 2 ) {
		throw ClipError( "Need formats" );
	}
	for ( int i = 2; i < cl.Argc(); i++ ) {
		string f = cl.Argv( i );
		long n;
		if ( ToInteger( f, n ) && HasFormat( n ) ) {
			return YesNo( 0 );
		}
		else if( HasFormat( f ) ) {
			return YesNo( 0 );
		}
	}
	return YesNo( 1 );
}

//----------------------------------------------------------------------------
// List formats currently in clipboard
//----------------------------------------------------------------------------

int  ListFormatsCmd( CmdLine & ) {
	Clipboard clip;
	UINT f = 0;
	bool showunk = true;
	while( (f = ::EnumClipboardFormats( f )) != 0 ) {
		if ( showunk || KnownFormat( f ) ) {
			cout << CFToStr( f ) << "\n";
		}
	}
	return 0;
}

//----------------------------------------------------------------------------
// Read standard input and set clipboard to read text
//----------------------------------------------------------------------------

int ReadCmd( CmdLine & ) {
	string lines;
	string line;
	while( getline( cin, line ) ) {
		lines += line;
		lines += "\n";
	}
	Clipboard clip;
	SetTextData( lines, AppendMode );
	return 0;
}

//----------------------------------------------------------------------------
// Anything in clipboard? Inverse of the empty command.
//----------------------------------------------------------------------------

int AnyCmd(  CmdLine & ) {
	Clipboard clip;
	if ( ::EnumClipboardFormats( 0 ) ) {
		return YesNo( 0 );
	}
	else {
		return YesNo( 1 );
	}
}

//----------------------------------------------------------------------------
// Find text in clipboard.
//----------------------------------------------------------------------------

int FindCmd(  CmdLine & cl ) {
	Clipboard clip;
	if ( HasFormat( CF_TEXT ) ) {
		if ( cl.Argc() == 2 ) {
			throw ClipError( "No text to find speciofied" );
		}
		string target;
		for ( int i = 2; i < cl.Argc(); i++ ) {
			target += cl.Argv( i );
		}
		string text = GetTextData();
		return text.find( target ) == string::npos ? YesNo( 1 ) : YesNo( 0 );
	}
	else {
		return YesNo( 1 );
	}
}

//----------------------------------------------------------------------------
// Monitor clipboard for changes
//----------------------------------------------------------------------------

int MonitorCmd(  CmdLine & cl ) {
	ClipboardMonitor cm( cl, Verbose );
	cm.Start();
	return 0;
}

//----------------------------------------------------------------------------
// Can the clipboard be opened?
//----------------------------------------------------------------------------

int OpenCmd(  CmdLine & ) {
	if ( ! ::OpenClipboard( NULL ) ) {
		return YesNo( 1 );
	}
	else {
		::CloseClipboard();
		return YesNo( 0 );
	}
	return 0;
}

//----------------------------------------------------------------------------
// List files that could be dropped - clipboard must contain CF_HDROP format.
//----------------------------------------------------------------------------

int DListCmd(  CmdLine & ) {

	Clipboard clip;
	if ( ! HasFormat( CF_HDROP ) ) {
		throw ClipError( "No CF_HDROP format data in the clipboard" );
	}
	vector<string> files = GetDropFiles();

	if ( files.size() == 0 ) { // is this even possible?
		throw ClipError( "Have CF_DROP format but no associated files" );
	}

	for( unsigned int i = 0; i < files.size(); i++ ) {
		cout << files[i] << "\n";
	}

	return 0;
}


//----------------------------------------------------------------------------
// Command to function mapping
//----------------------------------------------------------------------------

typedef int (*CommandFunc)( CmdLine & );

struct StrToCommand {
	const char * const cmd;
	CommandFunc func;
	const char * const help;
};

int HelpCmd( CmdLine & );	// because help command references the command map

StrToCommand CommandMap[] = {
	{ "any",	AnyCmd, "test if there is anything in the clipboard" },
	{ "bget",	BGetCmd, "display clipboard bitmap data" },
	{ "bset",	BSetCmd, "set bitmap into clipboard from file" },
	{ "cd",		CurrentDirCmd, "copy current directory path to clipboard" },
	{ "clear",	ClearCmd, "clear the clipboard of all content" },
	{ "dlist",  DListCmd, "list files that could be dropped" },
	{ "empty",	EmptyCmd, "test if the clipboard is empty" },
	{ "find",	FindCmd, "test if clipboard contains specified text" },
	{ "get",	GetDataCmd, "get contents of clipboard as text and display it" },
	{ "has", 	HasFormatsCmd, "test if clipboard contains listed formats" },
	{ "list", 	ListFormatsCmd, "list formats currently in clipboard" },
	{ "mon", 	MonitorCmd, "monitor clipboard for changes" },
	{ "msg", 	MsgBoxCmd, "display clipboard content in message box" },
	{ "open", 	OpenCmd, "test if the clipboard can be opened" },
	{ "read", 	ReadCmd, "read standard input and place in clipboard" },
	{ "set", 	SetCmd, "set clipboard to contain specified text" },
	{ "help", 	HelpCmd, "display this help" },
	{ 0, 0, 0 }	// terminator
};

//----------------------------------------------------------------------------
// Display help
//----------------------------------------------------------------------------

int HelpCmd(  CmdLine &  ) {
	cout << "nclip is a programmers utility for messing with the clipboard\n";
	cout << "Version " << VERSION << "\n";
	cout << "Copyright (C) 2012 Neil Butterworth\n\n";
	cout << "usage: nclip [-v] [-a] [command [...]]\n";
	cout << "\nwhere command is one of: \n\n";
	unsigned int i = 0;
	while( CommandMap[i].cmd ) {
		cout << setw( 6 ) << left << CommandMap[i].cmd << " " << CommandMap[i].help << "\n";
		i++;
	}
	cout << "\n-v    print 'yes' or 'no' for commands that test the clipboard\n";
	cout << "      for the mon command, display changes for text content\n";
	cout << "-a    for commands that set the clipboard, append rather than overwrite";
	cout << endl;
	return 0;
}

//----------------------------------------------------------------------------
// Given a command name, get functipn that implements it
//----------------------------------------------------------------------------

CommandFunc GetCommandFunc( const string & cms ) {
	unsigned int i = 0;
	while( CommandMap[i].cmd ) {
		if ( CommandMap[i].cmd == cms ) {
			return CommandMap[i].func;
		}
		i++;
	}
	return 0;
}

//----------------------------------------------------------------------------
// Process any flags that might be on the command line. Called if there
// is something on the command line.
//----------------------------------------------------------------------------

const char * const VERBOSE_FLAG = "-v";
const char * const APPEND_FLAG = "-a";

void ProcessFlags( CmdLine & cl ) {

	while( cl.Argc() > 1 && cl.Argv(1).substr( 0, 1 ) == "-" ) {
		if ( cl.Argv(1) == VERBOSE_FLAG ) {
			Verbose = true;
			cl.Shift();
			continue;
		}
		if ( cl.Argv(1) == APPEND_FLAG ) {
			AppendMode = true;
			cl.Shift();
			continue;
		}
		else {
			throw ClipError( "bad flag: " + cl.Argv(1) );

		}
	}
}

//----------------------------------------------------------------------------
// Main for the clip utility
//----------------------------------------------------------------------------

int main( int argc, char *argv[] ) {

	try {
		CmdLine cl( argc, argv );
		ProcessFlags( cl );
		if ( cl.Argc() == 1 ) {
			HelpCmd( cl );
		}
		else {
			CommandFunc f = GetCommandFunc( cl.Argv(1) );
			if ( f == 0 ) {
				throw ClipError( "Unknown command: " +  cl.Argv(1) );
			}
			return f( cl );
		}
		return 0;
	}

	catch( const ClipError & ex ) {
		cerr << ex.what() << endl;
		return 1;
	}
	catch( const std::exception & ex ) {
		cerr << "Unexpected std::exception" << endl;
		return 1;
	}
	catch( ... ) {
		cerr << "Unexpected unknown execption" << endl;
		return 1;
	}
}

// end

